#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
sys.path.append('../')
import regex
import copy

import click
import requests
import langdetect
import urllib

from utils import tp_logging
from utils import tp_config
from utils import tp_exceptions

import text_extractor


logger = tp_logging.get_logger('lyrics_parser')
import logging
logger.setLevel(logging.DEBUG)


class Session(object):
    def __init__(self):
        self.session = requests.Session()

    def get(self, url, **kwargs):
        """Establishes the default headers to be used in GET requests.

        Args:
            url (str): The url to be requested.

        Returns:
            requests.Response: HTTP response.
        """
        response = self.session.get(
            url=url,
            headers={
                'user-agent': (
                    'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:68.0) '
                    'Gecko/20100101 Firefox/68.0')
            },
            **kwargs
        )

        return response


class LyricsParser(object):
    def __init__(self):
        self.artist = None
        self.song = None
        self.session = Session()

    @staticmethod
    def fix_html_common_semantic_issues(html):
        """Fixes common HTML issues.

        Many lyrics websites have broken HTML tags. For example, some will
        use line breakers inside paragraphs in place of starting new ones etc.

        Args:
            html (str): HTML to be fixed.

        Returns:
            str: HTML resulted from fixing.
        """

        # 1 - <br /> tags inside <p>
        if regex.search('<p>.{1,50}<br', html) is not None:
            html = regex.sub('(<br.{0,3}>)', '</p><p>', html)

        # 2 - \r removal
        html = html.replace('\r', '')

        # 3 - Remove extra line breaks
        html = regex.sub('\n{2,}', '\n', html)

        return html

    def fetch_from_web(self, searx_results_index=0):
        """Gets and visits Searx results for specific keywords.

        Sends query combining keywords to be included and excluded in order to
        get results from Searx for the lyrics expected.

        Args:
            searx_results_index (int): Index in search results list.

        Returns:
            str: A summary of the visited result.
        """

        # Search anonymously with Searx, must have "lyrics",
        # must not have "youtube" etc

        must_not_have = [
            'youtube',
            'amazon',
            'wiki',
            'chords',
            'tabs'
        ]

        must_not_have = ' -'.join(must_not_have)

        search_engines = tp_config.get_config()['search_engines']

        must_search_engines = ' !'.join(search_engines)

        lang_id_detected = langdetect.detect(self.song.lower())

        payload_searx = {
            'q': (
                    '!{} "{}" "{}" "lyrics" filetype:html intitle:lyrics -{}'
                ).format(
                    must_search_engines,
                    self.artist,
                    self.song,
                    must_not_have),
            'categories': 'general',
            'format': 'json',
            'language': lang_id_detected
        }

        logger.debug('Language detected: %s', lang_id_detected)

        searx_response = self.session.get(
            url=tp_config.get_config()['searx_instance_url'],
            params=payload_searx)

        searx_json = searx_response.json()

        if type(searx_json['results']) is not list:
            return ''

        top_lyrics_url = searx_json['results'][searx_results_index]['url']

        logger.debug(top_lyrics_url)

        # The song title should be on the url

        remove_url_symbols = lambda url: regex.sub(
            r' |-|_',
            '',
            url
        ).lower()

        no_symbols_url = remove_url_symbols(top_lyrics_url)

        no_symbols_title = remove_url_symbols(self.song)

        # If the url doesn't contain the song's title

        if urllib.parse.quote(no_symbols_title).lower() not in no_symbols_url:
            logger.debug('The url doesn\'t contain the title of the song')
            return ''

        response_content = self.session.get(url=top_lyrics_url).text

        decoded_response_content = response_content

        html = self.fix_html_common_semantic_issues(
            html=decoded_response_content)

        return text_extractor.extract_summary(html)

    def fix_joined_lines(self, lyrics_lines):
        """Divides lines considered as one by the readability library.

        Sometimes, two or more phrases may be considered as one, for example:

        "Cupiditate consequatur quidem aut molestias
        Harum ipsam est omnis magnamNam quo nostrum eaque dolorem"

        There should be a third line starting from the word "Nam" in the second
        one:

        "Cupiditate consequatur quidem aut molestias
        Harum ipsam est omnis magnam
        Nam quo nostrum eaque dolorem"

        Args:
            lyrics_lines (list): List containing the lines of the lyrics.

        Returns:
            list: List of lyrics lines properly divided.
        """

        fixed_lyrics_lines = list()

        for line in lyrics_lines:

            ready_line = ''

            for char_index, char in enumerate(line):

                ready_char = char

                if (
                    char.upper() == char and # If it's a uppercase char
                    char_index != 0 and # First char of the line
                    regex.search('\w', char) is not None and
                    # If the previous char is not a space
                    regex.search('\s', line[char_index - 1]) is None):

                    ready_char = '\n{}'.format(ready_char)

                ready_line = '{}{}'.format(ready_line, ready_char)

            fixed_lyrics_lines+= ready_line.split('\n')

        return fixed_lyrics_lines

    def clean_lyrics_keywords(self, lyrics_summary):
        """Removes known common noise text found around lyrics.

        Excludes common keywords and symbols that are considered as part of the
        text by the readability library from the summary.

        Args:
            lyrics_summary (str): Summary with noise text.

        Returns:
            list: List of lines without noise text.
        """


        lyrics_lines = [line.strip() for line in lyrics_summary.split('\n')]

        lyrics_lines = self.fix_joined_lines(lyrics_lines)

        # Remove lines containing "lyrics", "chorus" etc, white spaces, empty
        # items
        copy_lines = copy.copy(lyrics_lines)

        for line_index, line in enumerate(copy_lines):
            # Remove song title from lyrics beginning
            if line_index == 0 and self.song == line:
                lyrics_lines.remove(line)

            # Remove keywords/symbols
            if regex.search(r'video|lyrics|chorus|writer|:|author|"|html|'
                            r'upload|'
                            r'<|>|' # HTML tags remnants
                            r'\{|\}|'
                            r'\s{2,}|'
                            r'^.{1}$|' # One char only
                            r'^(:?[$=\s]+)',
                            line,
                            regex.IGNORECASE
                            ) is not None or len(line) == 0:
                lyrics_lines.remove(line)

        return lyrics_lines

    def get_lyrics(self, artist, song, retries=5):
        """Gets lyrics results from Searx for the inputed artist and song.

        Executes the HTTP request and visit the first results received in the
        JSON file returned by Searx and then runs the cleanning steps for each
        one, until a summary with more than ten lines is found.

        Args:
            artist (str): The song's artist name.
            song (str): The song's title.

        Returns:
            list: List containing lyrics lines.
        """

        logger.info('%s - %s', artist, song)

        self.artist = artist
        self.song = song

        clear_lyrics = None

        # Try (retries number) different sources/Searx results
        for retry in range(0, retries):
            logger.debug('Trying Searx result %d', retry + 1)

            summary = None

            err = None

            try:
                summary = self.fetch_from_web(searx_results_index=retry)
            except requests.exceptions.ConnectionError:
                err = tp_exceptions.SearxInstanceDown()
            finally:
                if err is not None:
                    raise err

            clear_lyrics = self.clean_lyrics_keywords(lyrics_summary=summary)

            # More than 10 lines
            len_clear_lyrics = len(clear_lyrics)

            if len_clear_lyrics > 10:
                break

        if len(clear_lyrics) == 0:
            logger.info('No lyrics were found for "%s" - %s', artist, song)
            return

        return clear_lyrics

@click.command()
@click.option('--song', help='Song title', required=True)
@click.option('--artist', help='Artist name', required=True)
def get_lyrics_cli(song, artist):
    lyrics_parser = LyricsParser()
    lyrics = lyrics_parser.get_lyrics(song=song, artist=artist)
    if lyrics:
        from pprint import pprint
        pprint(lyrics)

if __name__ == '__main__':
    get_lyrics_cli()

